using System;
using System.Diagnostics;
#if TEST
using Xunit;
#endif

namespace Fadd.Logging
{
    /// <summary>
    /// Logger base class (provides helpers for stack frames)
    /// </summary>
    public abstract class Logger : ILogger
    {
        private readonly LogFormatter _formatter;
        private readonly ILoggerConfiguration _config;

        /// <summary>
        /// Initializes a new instance of the <see cref="Logger"/> class.
        /// </summary>
        /// <param name="formatter">used to format log entries.</param>
        /// <param name="configuration">Configures a logger.</param>
        protected Logger(LogFormatter formatter, ILoggerConfiguration configuration)
        {
            Check.Require(formatter, "formatter");
            Check.Require(configuration, "configuration");
            _formatter = formatter;
            _config = configuration;
        }

        /// <summary>
        /// Get a certain amount of stack frames.
        /// </summary>
        /// <returns></returns>
        protected StackFrame[] GetStackFrames()
        {
            StackTrace trace = new StackTrace();
            StackFrame[] allFrames = trace.GetFrames();
            if (allFrames == null)
                return null;

            if (allFrames.Length < _config.StackFrameStart)
                return null;

            int startFrame = _config.StackFrameStart;
            if (startFrame + _config.StackFrameCount >= allFrames.Length)
                _config.StackFrameCount = allFrames.Length - startFrame;

            StackFrame[] frames = new StackFrame[_config.StackFrameCount];
            for (int i = 0; i < _config.StackFrameCount; ++i)
                frames[i] = allFrames[startFrame + i];

            return frames;
        }

        /// <summary>
        /// Get an array with class.method names from the stack trace.
        /// </summary>
        /// <returns></returns>
        protected string[] GetStackNames()
        {
            StackTrace trace = new StackTrace();
            StackFrame[] allFrames = trace.GetFrames();
            if (allFrames == null)
                return null;

            if (allFrames.Length < _config.StackFrameStart)
                return null;

            int startFrame = _config.StackFrameStart;
            if (startFrame + _config.StackFrameCount >= allFrames.Length)
                _config.StackFrameCount = allFrames.Length - startFrame;

            string[] frames = new string[_config.StackFrameCount];
            for (int i = 0; i < _config.StackFrameCount; ++i)
                frames[i] = allFrames[startFrame + i].GetMethod().ReflectedType.Name + "." + allFrames[startFrame + i].GetMethod().Name;

            return frames;
        }

#if TEST
        [Fact]
        private void TestStackFrames()
        {
            _config.StackFrameStart = 1;
            _config.StackFrameCount = 4;
            string[] frames = GetStackNames();
            string text = string.Join(" --> ", frames);
            Assert.Equal("TestStackFrames --> _InvokeMethodFast --> InvokeMethodFast --> Invoke", text);
        }
#endif

        /// <summary>
		/// Format a log entry, uses <see cref="LogLevel.Debug"/>.
        /// </summary>
        /// <param name="message">Entry written by programmer.</param>
        /// <param name="error">Exception if any, may be null.</param>
        /// <returns>Formatted message</returns>
        protected string FormatEntry(string message, Exception error)
        {
            return _formatter.Format(message, error, GetStackNames());
        }

		/// <summary>
		/// Format a log entry.
		/// </summary>
		/// <param name="logLevel">Importance of message</param>
		/// <param name="message">Entry written by programmer.</param>
		/// <param name="error">Exception if any, may be null.</param>
		/// <returns>Formatted message</returns>
		protected string FormatEntry(LogLevel logLevel, string message, Exception error)
		{
			return _formatter.Format(logLevel, message, error, GetStackNames());
		}

        #region Implementation of ILogger

        /// <summary>
        /// Gets the configuration used for the logger
        /// </summary>
        /// <value></value>
        public ILoggerConfiguration LoggerConfiguration
        {
            get { return _config; }
        }

        /// <summary>
        /// Very detailed log messages, potentially of a high frequency and volume
        /// </summary>
        /// <param name="message">message written to the log.</param>
        public abstract void Trace(string message);

        /// <summary>
        /// Very detailed log messages, potentially of a high frequency and volume
        /// </summary>
        /// <param name="message">message written to the log.</param>
        /// <param name="exception">an exception.</param>
        public abstract void Trace(string message, Exception exception);

        /// <summary>
        /// Less detailed and/or less frequent debugging messages
        /// </summary>
        /// <param name="message">message written to the log.</param>
        public abstract void Debug(string message);

        /// <summary>
        /// Less detailed and/or less frequent debugging messages
        /// </summary>
        /// <param name="message">message written to the log.</param>
        /// <param name="exception">an exception.</param>
        public abstract void Debug(string message, Exception exception);

        /// <summary>
        /// Informational messages
        /// </summary>
        /// <param name="message">message written to the log.</param>
        public abstract void Info(string message);

        /// <summary>
        /// Informational messages
        /// </summary>
        /// <param name="message">message written to the log.</param>
        /// <param name="exception">an exception.</param>
        public abstract void Info(string message, Exception exception);

        /// <summary>
        /// Warnings which don't appear to the user of the application
        /// </summary>
        /// <param name="message">message written to the log.</param>
        public abstract void Warning(string message);

        /// <summary>
        /// Warnings which don't appear to the user of the application
        /// </summary>
        /// <param name="message">message written to the log.</param>
        /// <param name="exception">an exception.</param>
        public abstract void Warning(string message, Exception exception);

        /// <summary>
        /// Error messages
        /// </summary>
        /// <param name="message">message written to the log.</param>
        public abstract void Error(string message);

        /// <summary>
        /// Error messages
        /// </summary>
        /// <param name="message">message written to the log.</param>
        /// <param name="exception">an exception.</param>
        public abstract void Error(string message, Exception exception);

        /// <summary>
        /// Fatal error messages. After a fatal error, the application usually terminates. 
        /// </summary>
        /// <param name="message">message written to the log.</param>
        public abstract void Fatal(string message);

        /// <summary>
        /// Fatal error messages. After a fatal error, the application usually terminates. 
        /// </summary>
        /// <param name="message">message written to the log.</param>
        /// <param name="exception">an exception.</param>
        public abstract void Fatal(string message, Exception exception);

        #endregion
    }
}
